package lohr.lang.dom;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;



/**
 * Base class for all lohr language elements.
 * 
 * Additional properties can be associated with an element using the 
 * getProperty/setProperty methods.
 * 
 * @author ted stockwell
 */
abstract public class LohrElement {
	
	private List<LohrElement> _children;
	private LohrElement _parent= null;
	private Integer _hashcode;
	private HashMap<Object, Object> _properties; 
	
	protected void setParent(LohrElement parent) {
		_parent= parent;
		_hashcode= null;
	}
	
	protected void prependChild(LohrElement child) {
		if (child == null)
			return;
		if (_children == null)
			_children= new ArrayList<LohrElement>();
		if (_children.contains(child))
			return;
		_children.add(0, child);
		if (child._parent != this) {
			if (child._parent != null)
				child._parent.removeChild(child);
			child.setParent(this);
		}
		_hashcode= null;
	}
	protected void addChild(LohrElement child) {
		if (child == null)
			return;
		if (_children == null)
			_children= new ArrayList<LohrElement>();
		if (_children.contains(child))
			return;
		_children.add(child);
		if (child._parent != this) {
			if (child._parent != null)
				child._parent.removeChild(child);
			child.setParent(this);
		}
		_hashcode= null;
	}
	protected <T extends LohrElement> void addChildren(Collection<T> children) {
		for (LohrElement child:children)
			addChild(child);
	}
	
	protected void removeChild(LohrElement child) {
		if (_children == null)
			return;
		if (!_children.contains(child))
			return;
		_children.remove(child);
		child.setParent(null);
		_hashcode= null;
	}
	
	public List<LohrElement> getChildren() {
		if (_children == null)
			return Collections.emptyList();
		return Collections.unmodifiableList(this._children);
	}

	/**
	 * returns true if this node contains the given node
	 */
	public boolean contains(LohrElement node) {
		if (this == node)
			return true;
		if (_children != null) {
			for (LohrElement c:_children) {
				if (c.contains(node))
					return true;
			}
		}
		return false;
	}
	
    public int hashCode() {
    	if (_hashcode == null) {
        	int hashCode = super.hashCode();
//        	if (_parent != null)
//        		hashCode = 31*hashCode + _parent.hashCode();
        	if (_children != null) {
        		for (LohrElement child:_children)
        	    	hashCode= 31*hashCode + child.hashCode();
        	}
        	_hashcode= hashCode;
    	}
    	return _hashcode;
     }
	
	@Override
	public boolean equals(Object obj) {
		if (obj == this)
			return true;
		if (!(obj instanceof LohrElement))
			return false;
		LohrElement e= (LohrElement)obj;
		
		if (e._parent != _parent)
			return false;
		if (e._children == null) {
			if (_children != null)
				return false;
		}
		else {
			if (_children == null)
				return false;
			if (e._children.size() != _children.size()) 
				return false;
				for (int i= _children.size(); 0 < i--;) {
					LohrElement c1= e._children.get(i);
					LohrElement c2= _children.get(i);
					if (!c1.equals(c2))
						return false;
				}
		}

		return true;
	}

	public LohrElement getParent() {
		return _parent;
	}

	public LohrElement getFirstChild() {
		if (_children == null || _children.isEmpty())
			return null;
		return _children.get(0);
	}
	
	public LohrElement getLastChild() {
		if (_children == null || _children.isEmpty())
			return null;
		return _children.get(_children.size()-1);
	}
	

	public void accept(DOMVisitor visitor) {
		boolean visitChildren= visitor.visit(this);
		if (visitChildren) {
			for (LohrElement node : getChildren())
				node.accept(visitor);
		}
		visitor.complete(this);
	}

	/**
	 * @return A label that identifies the type of element
	 */
	public String getLabel() {
		return getClass().getSimpleName();
	}

	@SuppressWarnings("unchecked")
	public <T> T getProperty(String key) {
		if (_properties == null)
			return null;
		return (T) _properties.get(key);
	}
	
	public Object setProperty(Object key, Object value) {
		if (_properties == null)
			_properties= new HashMap<Object, Object>();
		return _properties.put(key, value);
	}
}
